/*
 * =====================================================================================
 *
 *       Filename:  cl_logger.cxx
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  04/12/2011 09:32:07 AM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  Chen Ken (Ken), zlchen.ken@gmail.com
 *        Company:  EMC
 *
 * =====================================================================================
 */

#ifndef __GXX_EXPERIMENTAL_CXX0X__
#define __GXX_EXPERIMENTAL_CXX0X__
#endif 

#include <sys/eventfd.h>
#include <cstdarg>
#include <cstdio> 
#include <cassert>
#include <atomic>
#include <string>
#include "cl_logger.hxx"
#include "cl_pc_queue.hxx"
#include "cl_thread.hxx"
#include "cl_mutex.hxx"
#include "cl_lock_guard.hxx"
#include "cl_timestamp.hxx"
#include "cl_lexical_cast.hxx"

namespace neo
{
using namespace std;

namespace
{
const char* logLevelStr[CLLogger::NUM_LOG_LEVELS] = 
{
  "DEBUG",
  "TRACE",
  "INFO",
  "WARNING",
  "CRITICAL",
  "ERROR",
  "FATAL",
};

inline CLLogger::CLLogLevel logLevel(void)
{
  char* level = NULL;
  if((level = ::getenv("CL_LOG_LEVEL")) != NULL)
  {
    return (CLLogger::CLLogLevel)clLexicalCast<int>(level);
  }

  return CLLogger::LOG_CRITICAL;
}

} /// namespace

/// Logger base impl
class CLLoggerBaseImpl
{
public:
  explicit CLLoggerBaseImpl(const string& filename) 
    : m_filename(filename),
    m_fp(NULL)
  {
    if(m_filename.empty())
    {
      m_fp = stdout;
    }
    else
    {
      m_fp = fopen(filename.c_str(), "ab");
    }
    assert(m_fp != NULL);
  }

  virtual ~CLLoggerBaseImpl()
  { 
    fflush(m_fp);
    fclose(m_fp);
    m_fp = NULL;
  };

  void log(const string& facility, 
           const CLLogger::CLLogLevel level, 
           const char* pszFormat,
           va_list args) 
  {
    // Check if the log level is turned on.
		if(m_fp == NULL) 
    {
      return;
    }

    // Gather log msg.
    char buf[1024*4];
    int siz = snprintf(buf, sizeof(buf), "%s: %s: %s ", 
                       CLTimestamp::nowTime().toFormatString().c_str(),
                       facility.c_str(), logLevelStr[level]);
 
    siz += vsnprintf(buf + siz, sizeof(buf) - siz, pszFormat, args);
    buf[siz++] = '\n';
    buf[siz] = '\0';
    doLog(buf, siz);
  }

protected:
  void doWriteLog(const char* logMsg, int siz)
  {
    int n = fwrite(logMsg, 1, siz, m_fp);
    (void)n;
  }
  
  void logShutdown(const string& facility, 
                   const CLLogger::CLLogLevel level, 
                   const char* pszFormat, ...) 
  {
    va_list args;
    va_start(args, pszFormat);
    log(facility, level, pszFormat, args);
    va_end(args);
  }

private:
  virtual void doLog(const char* buf, int siz) = 0; 

private:
  CLLoggerBaseImpl(const CLLoggerBaseImpl&);
  CLLoggerBaseImpl& operator=(const CLLoggerBaseImpl&);

protected:
  string m_filename;
  FILE* m_fp;
};

/// Async logger
class CLAsyncLoggerImpl: public CLLoggerBaseImpl
{
public:
  explicit CLAsyncLoggerImpl(const string& filename) 
    : CLLoggerBaseImpl(filename),
    m_shutdown(false),
    m_logQueue(new CLProducerConsumerQueue<string>) 
  {
    m_logThr = LogThread(new CLThread(bind(&CLAsyncLoggerImpl::writeLog, this)));
    m_logThr->start();
  }

  ~CLAsyncLoggerImpl()
  {
    m_shutdown = true;
    logShutdown("Logger", CLLogger::LOG_CRITICAL, "Log subsystem is shutting down...\n");
    m_logThr->join();
  }

private:
  virtual void doLog(const char* buf, int siz)  
  {
    m_logQueue->push(buf);
  }

  void writeLog(void)
  {
    while(!m_shutdown)
    {
      string logMsg;
      m_logQueue->pop(logMsg);
      doWriteLog(logMsg.c_str(), logMsg.size());
    }
  }

private:
  typedef unique_ptr<CLProducerConsumerQueue<string> > LogQueue;
  typedef unique_ptr<CLThread> LogThread;

private:
  atomic<bool> m_shutdown;
  LogQueue m_logQueue;
  LogThread m_logThr;
};

/// Sync Logger
class CLSyncLoggerImpl: public CLLoggerBaseImpl
{
public:
  explicit CLSyncLoggerImpl(const string& filename) 
    : CLLoggerBaseImpl(filename) 
  {
  }

  ~CLSyncLoggerImpl() 
  {
    logShutdown("Logger", CLLogger::LOG_CRITICAL, "Log subsystem is shutting down...\n");
  }

private:
  virtual void doLog(const char* buf, int siz) 
  {
	  // glibc, stdio functions are thread-safe
		// man flockfile.
    doWriteLog(buf, siz);
  }
};


///////////////////// CLLogger
CLLogger::CLLogger(const string& filename, bool asyncMode) 
{
  if(asyncMode)
  {
    m_logger.reset(new CLAsyncLoggerImpl(filename));
  }
  else
  {
    m_logger.reset(new CLSyncLoggerImpl(filename));
  }
}

CLLogger::~CLLogger() 
{
}

void CLLogger::log(const string& facility, 
                   const CLLogLevel level, 
                   const char* pszFormat, ...) 
{
  // Check if the log level is turned on.
  if(level < logLevel()) 
  {
    return;
  }

  va_list args;
  va_start(args, pszFormat);
  m_logger->log(facility, level, pszFormat, args);
  va_end(args);
}

/// global log instance
CLLogger logger__("", false);
} // neo

